feat(web-ui): extract shared shadcn-style package for apps/landing and apps/guides#2075
Conversation
Introduces a new private workspace package that apps/landing and apps/guides can share for shadcn-style components, the cn() helper, globals.css tokens, and a tailwind preset. Structure follows shadcn's official monorepo guide (https://ui.shadcn.com/docs/monorepo). This commit only adds the skeleton — no apps are migrated yet: - package.json (private, workspace:*, exports for components/lib/styles/tailwind) - tsconfig.json extending the monorepo conventions used by @packrat/analytics - src/lib/utils.ts canonical cn() helper (clsx + tailwind-merge) - src/components/button.tsx — first proof-of-concept component - src/index.ts barrel re-export - src/styles/globals.css and src/tailwind/preset.ts stubs (future PRs will move the shared base layer + theme tokens here) - Root tsconfig.json path aliases for @packrat/web-ui and @packrat/web-ui/*
Migrates apps/landing's Button component to re-export from the new @packrat/web-ui workspace package so all existing landing-app/components/ui/button callers keep working unchanged. - Adds @packrat/web-ui as a workspace:* dependency - Adds @packrat/web-ui and @packrat/web-ui/* path aliases to apps/landing/tsconfig.json (needed because the landing tsconfig does not extend the monorepo root) - Registers @packrat/web-ui in next.config.mjs transpilePackages so Next.js transpiles the package's TypeScript source - Adds ../../packages/web-ui/src glob to tailwind content so classes used by the shared Button are emitted - apps/landing/components/ui/button.tsx is now a 6-line re-export stub
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughA new shared UI package ( Changes
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@copilot fix failing build and ci checks |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Agent-Logs-Url: https://github.com/PackRat-AI/PackRat/sessions/8e5068b2-ae2e-487b-92bb-5d3c2a4b84ed Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
Agent-Logs-Url: https://github.com/PackRat-AI/PackRat/sessions/8e5068b2-ae2e-487b-92bb-5d3c2a4b84ed Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
Fixed in ba33f6b. The CI was failing because the PR merge commit included files from the base branch that had 3 biome errors:
Biome check now passes locally with 0 errors (165 pre-existing warnings). |
|
@claude re open |
There was a problem hiding this comment.
Pull request overview
Introduces a new shared workspace package @packrat/web-ui to begin deduplicating shadcn-style web UI between apps/landing and apps/guides, and includes a small landing-only proof-of-concept migration (Button). This PR also contains unrelated Expo app changes (trips location store behavior, map remount fix, and pack duplication UI).
Changes:
- Add
packages/web-uiworkspace package withcn(), a sharedButton, and stubs forglobals.css+ Tailwind preset; wire up TS path aliases. - Migrate
apps/landing’sButtonto re-export from@packrat/web-ui, and update Next/Tailwind configs to consume the package. - Update Expo trips/pack screens: stabilize MapView remounting, adjust trip location store behavior, and add pack duplication capability + UI.
Reviewed changes
Copilot reviewed 28 out of 30 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Adds root TS path aliases for @packrat/web-ui. |
| scripts/lint/no-circular-deps.ts | Refactors trailing-glob regex into a constant. |
| scripts/check-all.ts | Refactors regex patterns and string formatting in summary extraction. |
| packages/web-ui/package.json | Defines the new private @packrat/web-ui package exports and deps. |
| packages/web-ui/tsconfig.json | Adds strict TS config + local path aliases for the package. |
| packages/web-ui/src/index.ts | Barrel exports for Button and cn(). |
| packages/web-ui/src/lib/utils.ts | Introduces canonical cn() helper (clsx + tailwind-merge). |
| packages/web-ui/src/components/button.tsx | Adds shared shadcn-style Button implementation. |
| packages/web-ui/src/styles/globals.css | Adds stub shared globals stylesheet (future tokens). |
| packages/web-ui/src/tailwind/preset.ts | Adds stub Tailwind preset (future shared theme). |
| bun.lock | Updates lockfile to include the new workspace package and dependency resolutions. |
| apps/landing/package.json | Adds @packrat/web-ui workspace dependency. |
| apps/landing/tsconfig.json | Adds app-level TS path aliases for @packrat/web-ui. |
| apps/landing/next.config.mjs | Adds @packrat/web-ui to transpilePackages. |
| apps/landing/tailwind.config.js | Adds packages/web-ui/src to Tailwind content globs. |
| apps/landing/components/ui/button.tsx | Replaces local implementation with re-export stub from @packrat/web-ui. |
| apps/landing/components/site-footer.tsx | Minor layout tweak (removes lg:ml-auto). |
| apps/landing/components/sections/testimonials.tsx | Adjusts quote icon positioning + overflow clipping. |
| apps/landing/components/sections/landing-hero.tsx | Centers hero content on mobile; adjusts alignment utilities. |
| apps/landing/components/sections/how-it-works.tsx | Updates connector line to show only on lg 3-col layout. |
| apps/landing/components/sections/feature-section.tsx | Removes redundant wrapper to avoid double card styling. |
| apps/landing/components/sections/download.tsx | Removes client-side scroll handler; uses external links with safe rel attrs; centers content on mobile. |
| apps/expo/features/trips/store/tripLocationStore.ts | Makes location name optional and memoizes setter with useCallback. |
| apps/expo/features/trips/screens/TripDetailScreen.tsx | Adds stable key to MapView to force remount when coordinates change. |
| apps/expo/features/trips/components/TripForm.tsx | Adds effect to seed/clear location store based on trip ID. |
| apps/expo/features/packs/screens/PackListScreen.tsx | Passes showDuplicateButton into PackCard based on selected filter. |
| apps/expo/features/packs/hooks/useDuplicatePack.ts | Adds duplication hook that fetches a pack and clones it locally. |
| apps/expo/features/packs/hooks/useCreatePackFromPack.ts | Adds helper hook to clone a pack + its items into local stores. |
| apps/expo/features/packs/hooks/index.ts | Re-exports the new hooks. |
| apps/expo/features/packs/components/PackCard.tsx | Adds duplicate button UI (and loading state) for non-owned packs; refactors tags row layout. |
| // Set location from trip, or null if trip has no location | ||
| setLocation(trip?.location ?? null); | ||
|
|
||
| // Cleanup: clear location when component unmounts | ||
| return () => { |
There was a problem hiding this comment.
The new effect seeds the shared location store from trip?.location, but the existing effect later in this component (useEffect(() => { tripLocationStore.set(null) ... }, [])) will run after this one on mount and immediately clear the store back to null. That makes the seeded location unavailable when editing an existing trip.
Consider removing the older “reset store on mount” effect, or folding the stale-state reset into this effect (e.g., only clear on mount when !trip, or clear first and then seed), so the final mounted state matches trip?.location when editing.
| // Set location from trip, or null if trip has no location | |
| setLocation(trip?.location ?? null); | |
| // Cleanup: clear location when component unmounts | |
| return () => { | |
| const nextLocation = trip?.location ?? null; | |
| // Defer the seed so the final mounted store state matches the current | |
| // trip, even if another mount-only effect in this component clears the | |
| // shared location store during the same commit. | |
| const timeoutId = setTimeout(() => { | |
| setLocation(nextLocation); | |
| }, 0); | |
| // Cleanup: cancel pending seed and clear location when component unmounts | |
| return () => { | |
| clearTimeout(timeoutId); |
| name: packData.name || `${sourcePack.name} (Copy)`, | ||
| description: packData.description || sourcePack.description, |
There was a problem hiding this comment.
packData.name / packData.description are merged using ||, which will treat empty strings as “not provided” and fall back to the source pack values. If callers ever intentionally pass an empty string (e.g., to clear description), this will be impossible.
Use nullish coalescing (??) for string fields so only undefined/null trigger the fallback.
| name: packData.name || `${sourcePack.name} (Copy)`, | |
| description: packData.description || sourcePack.description, | |
| name: packData.name ?? `${sourcePack.name} (Copy)`, | |
| description: packData.description ?? sourcePack.description, |
| "./components/*": "./src/components/*.tsx", | ||
| "./lib/utils": "./src/lib/utils.ts", | ||
| "./styles/globals.css": "./src/styles/globals.css", | ||
| "./tailwind/preset": "./src/tailwind/preset.ts" |
There was a problem hiding this comment.
@packrat/web-ui exports the Tailwind preset as a TypeScript source file (./tailwind/preset -> ./src/tailwind/preset.ts). Tailwind configs are often executed by Node (and apps/landing currently uses a CJS tailwind.config.js), so consuming this via require('@packrat/web-ui/tailwind/preset') will fail unless you add a TS runtime loader or ship compiled JS.
To avoid future integration issues, consider exporting a JS/ESM file for the preset (or add a build step that emits JS + d.ts and point exports at the emitted files).
| "./tailwind/preset": "./src/tailwind/preset.ts" | |
| "./tailwind/preset": { | |
| "types": "./src/tailwind/preset.ts", | |
| "import": "./dist/tailwind/preset.js", | |
| "default": "./dist/tailwind/preset.js" | |
| } |
| // @ts-ignore: Safe because Legend-State uses Proxy | ||
| packsStore[newPackId].set(newPack); |
There was a problem hiding this comment.
This hook uses // @ts-ignore and direct index access on Legend-State stores (packsStore[newPackId].set(...), packItemsStore[newItemId].set(...)). Elsewhere in the packs feature the established pattern is to use the obs(store, id) helper (e.g. apps/expo/features/packs/hooks/useCreatePack.ts:21, useCreatePackItem.ts:31) which preserves typing and avoids suppressing errors.
Consider switching these writes to obs(packsStore, newPackId).set(...) / obs(packItemsStore, newItemId).set(...) and dropping the @ts-ignore comments.
Summary
Introduces a new private workspace package
@packrat/web-uisoapps/landingandapps/guidescan share shadcn-style components, thecn()helper,globals.csstokens, and a tailwind preset instead of each maintaining their own duplicated copies.Chained on #2052 (landing redesign) because that PR already brings landing and guides styles very close to identical. This PR formalizes that alignment into a shared package.
Structure follows shadcn's official monorepo guide.
Scope of this PR
Intentionally small: the package skeleton plus one proof-of-concept component (
Button) migrated inapps/landingonly. Everything else stays in each app for now — migration is the subject of future PRs.What's in
@packrat/web-uipackage.json— private,workspace:*, exports for.,./components/*,./lib/utils,./styles/globals.css,./tailwind/presettsconfig.json— matches the monorepo conventions used by@packrat/analyticssrc/lib/utils.ts— canonicalcn()helper (clsx + tailwind-merge)src/components/button.tsx— first migrated component, importscnfrom@packrat/web-ui/lib/utilssrc/index.ts— barrel re-exportsrc/styles/globals.css— stub (future PRs move base layer + theme tokens here)src/tailwind/preset.ts— stub Tailwind preset (future PRs move theme tokens here)Root-level wiring
tsconfig.jsongains@packrat/web-uiand@packrat/web-ui/*path aliasesMigration in
apps/landing"@packrat/web-ui": "workspace:*"toapps/landing/package.json@packrat/web-uipath aliases toapps/landing/tsconfig.json(the app tsconfig does not extend root, so it needs its own entries)@packrat/web-uitonext.config.mjstranspilePackagesso Next.js processes the package's TypeScript source../../packages/web-ui/src/**/*.{ts,tsx}totailwind.config.jscontentso classes used by the shared Button are emittedapps/landing/components/ui/button.tsxis now a 6-line re-export stub (export { Button, buttonVariants, type ButtonProps } from '@packrat/web-ui') — all 11 existing callers oflanding-app/components/ui/buttoncontinue to work unchangedapps/guidesis untouched in this PR.Full migration plan (follow-up PRs)
Phase 1 — Migrate
apps/landingcomponents onto@packrat/web-ui(this PR:button.tsxonly)Still living in
apps/landing/components/ui/and need to move topackages/web-ui/src/components/(re-exporting from the landing file or deleting the landing file and updating callers):Landing-only (likely stay in
apps/landing/components/ui/unless we decide to share):animated-gradient-border.tsx,animated-gradient-text.tsx,device-mockup.tsx,feature-card.tsx,glass-card.tsx,gradient-background.tsx,gradient-border-card.tsx,gradient-text.tsxPhase 2 — Migrate
apps/guidesonto@packrat/web-uiDiff of
apps/guides/components/ui/vsapps/landing/components/ui/:chart.tsx,use-mobile.tsx,use-toast.tsOnce Phase 1 is done, migrating
apps/guidesshould be mostly just adding the workspace dep, path alias,transpilePackagesentry, tailwind content glob, and deleting its duplicatedcomponents/ui/*files.Phase 3 — Deduplicate
globals.css,tailwind.config, andlib/utils.tspackages/web-ui/src/styles/globals.cssand@importit from each app'sapp/globals.css. See the diff notes — most of the divergence is app-specific Apple-style helpers (landing) vs container + font-feature-settings (guides)packages/web-ui/src/tailwind/preset.tswith shared theme extend tokens so bothapps/landing/tailwind.config.jsandapps/guides/tailwind.config.tscan justpresets: [require('@packrat/web-ui/tailwind/preset').default]apps/*/lib/utils.tscn()helpers and update callers to import from@packrat/web-ui/lib/utils(or just@packrat/web-ui)components.jsonto point at the shared globals.css per shadcn's guideTest plan
bun installresolves the new packagebun run check-typescleanbun lint— my touched files produce zero diagnostics (the remaining ~170 warnings are pre-existing on the base branch)apps/landingNext.js build compiles successfully (✓ Compiled successfully in 26.4s). The subsequent/500prerender error is pre-existing on the base branch — confirmed by runningbun run buildon HEAD without my changes and reproducing the sameTypeError: Cannot read properties of null (reading 'useRef')error at the same prerender stepapps/landingButton renders identically after the re-export swapSummary by CodeRabbit